package jp.reflexworks.invoice.util;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOCanRetryException;
import javax.jdo.JDOException;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;

import jp.reflexworks.gae.util.Condition;
import jp.reflexworks.gae.util.EntityConverter;
import jp.reflexworks.gae.util.KeyUtils;
import jp.reflexworks.gae.util.PMF;
import jp.reflexworks.gae.util.QueryUtils;
import jp.reflexworks.invoice.model.Invoice;
import jp.reflexworks.invoice.model.InvoiceBase;
import jp.reflexworks.invoice.model.Order;
import jp.sourceforge.reflex.util.FieldMapper;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;


public class JdoUtils {

	Logger logger = Logger.getLogger(this.getClass().getName());

	// Utils
	KeyUtils keyUtils = new KeyUtils(InvoiceBase.class);
	
	public static final int DEFAULT_PAGESIZE = 100;
	
	
	/*
	 * InvoiceをPKであるinvoiceNoで１件検索する
	 */
	public Invoice getInvoiceByKey(Invoice param) {

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Invoice invoice = pm.detachCopy(pm.getObjectById(Invoice.class,keyUtils.getChildKey(Invoice.class, param.invoiceNo)));
		pm.close();
		
		return  invoice;
	}


	/*
	 * gaeのdatastoreのqueryを使用して検索する
	 */
	public Invoice getInvoiceById(Invoice param) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

		Query query = new Query(Invoice.class.getSimpleName());
		query.addFilter("id", Query.FilterOperator.EQUAL , param.id);
		
		Entity entity = datastore.prepare(query).asSingleEntity();
		EntityConverter entityConverter = new EntityConverter();
		if (entity!=null) {
			return (Invoice) entityConverter.convert(Invoice.class, entity);
		}
		return null;

	}

	/*
	 * invoiceListに明細レコードをつける
	 */
	public void complementInvoiceRecord(List<Invoice> invoiceList) {
		
		for(Invoice invoice:invoiceList) {
			invoice.setOrderList(getOrdersByInvoiceNo(invoice.invoiceNo));
		}
	}

	/*
	 * 明細レコードをinvoiceNoで検索する
	 */
	public List<Order> getOrdersByInvoiceNo(String invoiceNo) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

		Query query = new Query(Order.class.getSimpleName());
		query.addFilter("invoiceNo", Query.FilterOperator.EQUAL , invoiceNo);
		
		Iterable<Entity> resultIterable = datastore.prepare(query).asIterable();
		EntityConverter entityConverter = new EntityConverter();
		
		Comparator comparator = new Comparator() {

			public int compare(Object o1, Object o2) {
				if (((Order) o1).getSeqno()==null||((Order) o1).getSeqno().equals("")) {
					if (((Order) o2).getSeqno()==null||((Order) o2).getSeqno().equals("")) return 0;
					else return 1;
				}else {
					if (((Order) o2).getSeqno()==null||((Order) o2).getSeqno().equals("")) return -1;
					else {
						return ((Order) o1).getSeqno().compareTo(((Order) o2).getSeqno());
					}
				}
			}

		};
		
		return entityConverter.convert(Order.class, resultIterable,comparator);

	}
/*
	 * gaeのdatastoreのqueryを使用して検索する
	 */
	public List<Invoice> getEntriesByParam(Invoice param, String pagesize, String nextId) {

/*		
		// ここから検索用条件クラス作成
		class ConditionImpl implements Condition{

			public String dateString;

			public ConditionImpl(String dateString) {
				this.dateString = dateString;
			}
			
			@Override
			public boolean doCheck(Object param) {
				
				// 条件にヒットした場合のみ、resultに追加する
				if (((Invoice)param).issuedDate!=null&&((Invoice)param).issuedDate.equals(dateString)) {
					return true;
				}

				return false;
			}
		};
		//ここまで
		
		ConditionImpl condition = new ConditionImpl("20090725");

		return getEntriesByParam(param, pagesize, nextId, condition);
*/		
		return getEntriesByParam(param, pagesize, nextId, null);
	}
	
	public List<Invoice> getEntriesByParam(Invoice param, String pagesize, String nextId,Condition condition) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

		Query query = new Query(Invoice.class.getSimpleName());

		// 検索項目（Indexがあるもの） を指定してnewする
		QueryUtils queryUtils = new QueryUtils(new String[]{"invoiceNo","companyName","job","issuedDate"});
		// paramの検索項目がnullでなければaddFiler(FilterOperator.EQUAL)される
		queryUtils.setParam(param, query);

		if (nextId != null) {
			query.addFilter("id", Query.FilterOperator.GREATER_THAN , Long.parseLong(nextId));
		}
		query.addSort("id");
		int limit = DEFAULT_PAGESIZE;

		if (pagesize != null) {
			limit = Integer.parseInt(pagesize);
		}

		// Fetch Option
		FetchOptions fetchOptions = FetchOptions.Builder.withLimit(limit);
		Iterable<Entity> resultIterable = datastore.prepare(query).asIterable(fetchOptions);

		EntityConverter entityConverter = new EntityConverter();
		List<Invoice> result = entityConverter.convert(Invoice.class, resultIterable,null,condition);

		return result;
	}

	/**
	 * 登録処理（複数件）
	 * @param List<Invoice> invoiceList
	 * @throws Exception 
	 */
	public void insert(List<Invoice> invoiceList)  {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx = pm.currentTransaction();
		int NUM_RETRIES = 3;
		
		try {
		for (int r = 0; r < NUM_RETRIES; r++) {
			try {
			tx.begin();
			logger.info("tx begin.");
			
			InvoiceBase invoiceBase;
			boolean invoiceBaseIsNew = true;
			try {
				invoiceBase = (InvoiceBase) pm.getObjectById(InvoiceBase.class,keyUtils.getBaseKeyBuilder().getKey());
				invoiceBaseIsNew = false;
				
			}catch(JDOObjectNotFoundException e){
				invoiceBase = new InvoiceBase();
				invoiceBase.setKey(keyUtils.getBaseKeyBuilder().getKey());
			}

			for (Invoice invoice:invoiceList) {
			if (invoice!=null) {
				invoiceBase.setInvoiceId(invoiceBase.getInvoiceId()+1);
				invoice.setId(invoiceBase.getInvoiceId());

				// invoiceはinvoiceNoがPKなのでそれを入れている
				invoice.setKey(keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));
				
				List<Order> orderList = invoice.getOrderList();
				if (orderList!=null&&orderList.size()>0) {

					long orderid = invoiceBase.getOrderId();
					for (Order order:orderList) {
						orderid++;
						order.setId(orderid);

						// keyの生成	
						KeyFactory.Builder keybuiler = keyUtils.getChildKeyBuilder(Invoice.class,invoice.invoiceNo);
						Key key = (keybuiler.addChild(Order.class.getSimpleName(), keyUtils.getRecordKeyName(orderid))).getKey();
						order.setKey(key);

						order.setInvoiceNo(invoice.invoiceNo);
					}
					invoiceBase.setOrderId(orderid);

				}
				// これでinvoiceがpersistent
				invoiceBase.addInvoice(invoice);
			}
			}
			
			if (invoiceBaseIsNew) {
				pm.makePersistent(invoiceBase);
			}

			logger.info("commit.");
			tx.commit();
			break;

		} 
		catch (JDOCanRetryException e) {
			if (r == (NUM_RETRIES - 1)) {
				throw e;
			}
			logger.info("rollbacked.");
			try {
				tx.rollback();
			} catch (Throwable ee) {}
		}
		catch (JDOException e) {
			logger.info("JDOException:"+e.getMessage());
			throw e;
		}
		catch (ConcurrentModificationException e) {
			logger.info("ConcurrentModificationException:"+e.getMessage());
			throw e;
		}
		} //　for loop

		}finally {
			if (tx.isActive()) {
				logger.info("rollbacked.");
				try {
					tx.rollback();
				} catch (Throwable e) {}
			}
			logger.info("pm closed.");
			try {
				pm.close();
			} catch (Throwable e) {}
		}
	}

	/**
	 * 登録処理（1件）
	 * @param invoice invoice
	 * @throws Exception 
	 */
	public void insert(Invoice invoice)  {
		List<Invoice> invoiceList = new ArrayList<Invoice>();
		invoiceList.add(invoice);
		insert(invoiceList);
	}

	/**
	 * 更新処理
	 * @param invoice product
	 * @throws Exception 
	 */
	public void update(Invoice invoice)  {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		FieldMapper fieldMapper = new FieldMapper();
		Transaction tx = pm.currentTransaction();
		int NUM_RETRIES = 3;

		try {
		for (int r = 0; r < NUM_RETRIES; r++) {
		try {
			tx.begin();
			logger.info("tx begin.");
			
			if (invoice!=null) {
				
				try {
					
					Invoice target = pm.getObjectById(Invoice.class,keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));

					// 業務アプリによる楽観的ロック
					if (target.getRevision()!=invoice.getRevision()) {
						
						throw new ConcurrentModificationException();
					}else {
						
						// 更新があるものだけをセット
						fieldMapper.setValue(invoice,target);
						// 更新カウントアップ
						target.setRevison(target.getRevision()+1);
						
					}
					
				}catch(JDOObjectNotFoundException e){
					// 0件更新
					throw e;
				}
			}
			

			logger.info("commit.");
			tx.commit();
			break;

		} 
		catch (JDOCanRetryException e) {
			if (r == (NUM_RETRIES - 1)) {
				throw e;
			}
			logger.info("rollbacked.");
			try {
				tx.rollback();
			} catch (Throwable ee) {}
		}
		catch (JDOException e) {
			logger.info("JDOException:"+e.getMessage());
			throw e;
		}
		catch (ConcurrentModificationException e) {
			logger.info("ConcurrentModificationException:"+e.getMessage());
			throw e;
		}
		} //　for loop
		}finally {
			if (tx.isActive()) {
				logger.info("rollbacked.");
				try {
					tx.rollback();
				} catch (Throwable e) {}
			}
			logger.info("pm closed.");
			try {
				pm.close();
			} catch (Throwable e) {}
		}
	}

	/**
	 * 削除処理(論理削除)
	 * @param invoice invoice
	 * @throws Exception 
	 */
	public void delete(Invoice invoice)  {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx = pm.currentTransaction();
		int NUM_RETRIES = 3;

		try {
		for (int r = 0; r < NUM_RETRIES; r++) {
		try {
			tx.begin();
			logger.info("tx begin.");

			if (invoice!=null) {
				try {
					Invoice target = pm.getObjectById(Invoice.class,keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));
					target.setDeleted(1);	// 1 = deleted
				}catch(JDOObjectNotFoundException e){
					// 0件更新
					throw e;
				}
			}
			
			logger.info("commit.");
			tx.commit();
			break;

		} 
		catch (JDOCanRetryException e) {
			if (r == (NUM_RETRIES - 1)) {
				throw e;
			}
			logger.info("rollbacked.");
			try {
				tx.rollback();
			} catch (Throwable ee) {}
		}
		catch (JDOException e) {
			logger.info("JDOException:"+e.getMessage());
			throw e;
		}
		catch (ConcurrentModificationException e) {
			logger.info("ConcurrentModificationException:"+e.getMessage());
			throw e;
		}
		} //　for loop
		}finally {
			if (tx.isActive()) {
				logger.info("rollbacked.");
				try {
					tx.rollback();
				} catch (Throwable e) {}
			}
			logger.info("pm closed.");
			try {
				pm.close();
			} catch (Throwable e) {}
		}
	}

	/**
	 * 削除処理（物理削除）
	 * @param invoice
	 * @throws Exception
	 */
	public void deleteCompletely(Invoice invoice)  {

		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

		try {
			if (invoice!=null) {
				datastore.delete(keyUtils.getChildKey(Invoice.class,invoice.invoiceNo));
			}
		} catch (IllegalArgumentException e) {
			// 何もしない
		} catch (ConcurrentModificationException e) {
			// 何もしない
		}
	}

}
